//-----------------------------------------------------------------------
// <copyright company="Olson Software Limited">
//      (c) Copyright Olson Software Limited.
//      This source is subject to the Microsoft Public License (Ms-PL).
//      Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
//      All other rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Reflection;
using System.ServiceModel.DomainServices.Client.ApplicationServices;

namespace Insight.Library.Navigation
{
    public class AuthenticatingUriMapper : CustomUriMapper {
        /// <summary>
        /// this event is called when the URI mapper needs to authenticate
        /// </summary>
        public event EventHandler<AuthenticationRequiredEventArgs> AuthenticationRequired;

        /// <summary>
        /// this function is called before any URI is returned. This gives you the opportunity to do any custom work, 
        /// such as authentication, prior to the URI being returned to the navigation system
        /// </summary>
        /// <param name="unmappedUri">the URI that was provided by the navigation system, before it was mapped</param>
        /// <param name="mappedUri">the URI, after mapping</param>
        /// <returns>the URI to navigate to, by default this is the same as the mapped URI</returns>
        protected override Uri CheckCanNavigateToUri(Uri unmappedUri, Uri mappedUri)
        {
            try
            {
                Type typeofPage = PageLoader.GetTypeFromUri(mappedUri);

                // check for authentication required
                MemberInfo inf = typeofPage;
                if (inf == null) return mappedUri;

                if (AuthenticationRequired == null)
                {
                    throw new InvalidOperationException("you must have an event handler for AuthenticationRequired");
                }

                object[] attributes;
                AuthorizationAttribute failure = CanDisplayPage(typeofPage, out attributes);
                // if one of the attributes has failed to authorize, we need to notify anyone who cares
                if (failure != null)
                {
                    AuthenticationRequiredEventArgs eventArgs = new AuthenticationRequiredEventArgs(this, typeofPage,
                                                                                                    Frame,
                                                                                                    attributes,
                                                                                                    unmappedUri,
                                                                                                    mappedUri,
                                                                                                    failure);
                    AuthenticationRequired(this, eventArgs);

                    if (eventArgs.CancelNavigation)
                    {
                        return CancelNavigation();
                    }
                }
            }
// ReSharper disable EmptyGeneralCatchClause
            catch (Exception)
// ReSharper restore EmptyGeneralCatchClause
            {
                
            }
            return mappedUri;
        }

        public AuthorizationAttribute CanDisplayPage(Type typeofPage, out object[] attributes)
        {
            if (typeofPage==null) throw new ArgumentNullException("typeofPage");

            attributes = typeofPage.GetCustomAttributes(typeof(AuthorizationAttribute), false);
            // check for Authentication/Roles
            return CheckAttributesForPage(typeofPage, attributes);
        }

        public AuthorizationAttribute CheckAttributesForPage(Type typeofPage, object[] attributes)
        {
            AuthorizationAttribute failure=null;
            foreach (AuthorizationAttribute attribute in attributes)
            {
                if (attribute.Authorize( WebContextBase.Current.Authentication.User)) continue;

                // give derived classes the chance to override this particular attribute 
                // on this particular page
                if (CheckAttribute(typeofPage, attribute)) continue;

                failure = attribute;
                break;
            }
            return failure;
        }

        /// <summary>
        /// used by derived classes to look at a particular attribute and decide if it wishes 
        /// to override the authorization failure.  this class is called only after authorization
        /// has failed for that particular attribute
        /// </summary>
        /// <param name="type">the type of page currently being examined</param>
        /// <param name="attribute">the attribute on which the failure occurred</param>
        /// <returns>false to accept the failure of the authentication, true to ignore this authentication failure</returns>
        protected virtual bool CheckAttribute(Type type, AuthorizationAttribute attribute)
        {
            return false;
        }
    }
}